#pragma once 
#include<iostream>
#include<queue>
#include<pthread.h>
using namespace std;

//由于存放数据的容器中可以存放各种数据类型，所以要用模板

//阻塞队列是共享资源，所以所有的线程都要看到，所以生产者消费者线程创建的时候都会把同一个阻塞队列的地址值传进线程的执行函数中
//这样生产者和消费者才能操作同一个阻塞队列
template<class T>
class BlockQueue
{
    static const int defaultnum=20;//默认的数据容量
public:
    BlockQueue(int maxcap=defaultnum):maxcap_(maxcap)
    {
        pthread_mutex_init(&lock_,nullptr);//可以直接使用init接口声明并且初始化一个锁
        pthread_cond_init(&c_cond_,nullptr);//定义消费者的条件变量
        pthread_cond_init(&p_cond_,nullptr);//定义生产者的条件变量
       
        //我们也可以增加额外的机制来控制生产者与消费者模型
        //这里使用水位机制，当水位低于low_water的时候，就让生产者多执行，当水位过高与high_water的时候就要让消费者多执行，当水位合适的时候就让生产者消费者自己竞争执行
        low_water_=maxcap_/3;
        high_water_=(maxcap_*2)/3;
    }

    //生产者和消费者之间是互相唤醒机制：

    //生产者对队列的push操作：
    void push(const T&in)
    {
        pthread_mutex_lock(&lock_);//想要push数据首先要竞争锁
        /*
        为什么不用if作为判断呢，是为了防止误唤醒的情况，如果有生产者被误唤醒，那么它进行后续的push操作会覆盖数据导致出错
        if(q_.size()==maxcap_)
        {}
        */
        //使用循环判断，做到防止误唤醒(伪唤醒)，增强代码的健壮性
        //注意：判断条件也是在访问资源，在唤醒后的线程只有竞争到了锁资源后才会进行下一轮的while判断，否则一直阻塞等待锁资源
        while(q_.size()>high_water_)//使用while循环判断，可以防止误唤醒的情况，如果有生产者线程被误唤醒了，它会继续判断条件是否满足，如果条件为真说明此时阻塞队列中的数据还是偏多的，所以就会重新加入到对应条件的等待队列中
        {
            pthread_cond_wait(&p_cond_,&lock_);//线程进入等待队列，并且释放锁资源
        }
        //执行到这说明此时该生产者线程持有锁，并且队列中数据并不算太多：此时就可以执行核心的数据push操作:
        q_.push(in);
        /*
        //增加了一个数据之后，要判断一下水位线：（该if操作是冗余的，有需要的时候删除即可）
        if(q_.size()>high_water_)
        {
            pthread_cond_signal(&c_cond_);//此时数据量高于水位线，所以要让消费者多执行，所以多唤醒一次消费者进程
        }
        */
        pthread_cond_signal(&c_cond_);//增加了一个数据就可以唤醒一个消费者线程了
        pthread_mutex_unlock(&lock_);//释放锁资源
    }

    //消费者得到数据：
    T pop()
    {
        pthread_mutex_lock(&lock_);
        //循环判断防止误唤醒：
        while(q_.size()<low_water_)//如果数据低于水位线了，消费者线程就会进入自己条件下的阻塞队列，因为此时的数据我们认为偏少
        {
            //进入等待队列后会自动释放锁
            pthread_cond_wait(&c_cond_,&lock_);//注意：被唤醒的线程会重新持有锁，如果此时锁资源不足，那么就会阻塞竞争锁资源
        }   
        //消费者线程能执行到这，说明此时仓库中数据没有偏少，并且该线程竞争到了锁资源
        T out=q_.front();
        q_.pop();
        /*
        if(q_.size()<low_water_)//判断水位线是否偏低（该if操作是冗余的，有需要的时候删除即可）
        {
            pthread_cond_signal(&c_cond_);//此时数据量低于水位线，所以要让生产者多执行，所以多唤醒一次生产者进程
        }
        */
        pthread_cond_signal(&p_cond_);
        pthread_mutex_unlock(&lock_);
        return out;
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&lock_);
        pthread_cond_destroy(&c_cond_);
        pthread_cond_destroy(&p_cond_);
    }

private:
    queue<T>q_;//共享的资源队列，也就是我们看做模型中的资源存储的仓库
    int maxcap_;
    pthread_mutex_t lock_;
    pthread_cond_t c_cond_;
    pthread_cond_t p_cond_;
    int low_water_;
    int high_water_;
};
